#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h> 
#include<string.h>
#include<errno.h>

#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUT_REDIR 2
#define APPEND_REDIR 3

char lineCommand [NUM];
char *myargv[OPT_NUM]; //指针数组，将控制台输入的字符串解析（切割）为一个一个选项 如："ls -a -l" -->  "ls" "-a" "-l"
int lastExitCode = 0;
int lastExitSignal = 0;

int redirType = NONE_REDIR;
char* redirFile = NULL;

void CommandCheck(char* commands)
{
  assert(commands != NULL);
  char *start = commands;
  char *end = start + strlen(commands);

  while(start < end)
  {
    if(*start == '>')
    {
      *start = '\0';
      start++;

      if(*start == '>')
      {
        //追加重定向
        *start = '\0';
        start++;
        redirType = APPEND_REDIR;
      }
      else 
      {
        //输出重定向
        redirType = OUT_REDIR; 
      }

      while(*start == ' ') start++;
      redirFile = start;
      break;
    }
    else if(*start == '<')
    {
      //"cat < file.txt"
      *start = '\0';
      while(*start  == ' ') start++;
      //开始填写重定向信息
      redirType = INPUT_REDIR;
      redirFile = start;
      break;
    }
    else 
    {
      start++;
    }
  }

}

int main()
{
  while(1)
  {
    //初始化
    redirFile = NULL;
    redirType = NONE_REDIR;
    errno = 0;
    //这是全局变量，如果不初始化的话，会导致上一次留下的信息

    //打印输出提示符
    printf("[用户名@主机名 当前路径]#");
    fflush(stdout);
  
    //获取用户输入,你自己会输入\n,这样会让缓冲区里还剩下一个\n
    char* s = fgets(lineCommand, sizeof(lineCommand)-1, stdin);
    //除去缓冲区里的\n, abcd\n
    lineCommand[strlen(lineCommand)-1] = '\0';
    assert(s != NULL);
    (void)s;
 

    // "ls -a -l -i"  ->  "ls" "-a" "-l" "-i" //exec必须由数组或者列表进行传参
    // ls -a -l > log.txt 拆成 ls -a -l 和 > >> < 和 log.txt 三个部分
    CommandCheck(lineCommand);
    // 将字符串切割为字串
    myargv[0] = strtok(lineCommand, " ");
    
    int i = 1;
    if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
    {
        myargv[i++]=(char*)"--color=auto";
    }
    
    //如果没有字串，strok会返回NULL，myargv[end]=NULL 
    while(myargv[i++] = strtok(NULL, " "));

    //cd命令无法更改目录，要进行修改
    //不需要创建子进程，直接让shell执行相应的命令，本质是执行系统接口
    //像这种，不需要我们的子进程来执行，而是让shell自己执行的命令叫做内置/内建命令
    if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
    {
      if(myargv[1] != NULL)
      {
        chdir(myargv[1]);
        //成功更改工作目录，直接重新执行shell
        //不能break
        continue;
      }
    }
    
    if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo") == 0)
    {
      if(strcmp(myargv[1], "$?") == 0)
      {
        printf("ecitcode:%d exitsignal:%d\n", lastExitCode, lastExitSignal);
      }
      else 
      {
        printf("%s\n",myargv[1]);
      }
      //无需继续执行
      continue;
    }

    #ifdef DEBUG 
    //测试是否成功切割
    for(int i = 0; myargv[i]; i++)
    {
      printf("myargv[%d]: %s\n", i, myargv[i]);
    }
    #endif

    //执行命令
    pid_t id = fork();
    //printf("%d\n",id);
    
    assert(id != -1);
    if(id == 0)
    {
      // 因为命令是子进程执行的，真正重定向的工作由子进程完成 
      // 如何重定向，需要父亲给子进程提供信息
      // 这里子进程重定向会影响对应的父进程吗？ 不会！因为进程具有独立性
      switch(redirType)
      {
        case NONE_REDIR:
            break;
        case INPUT_REDIR:
            {
              int fd = open(redirFile, O_RDONLY);
              if(fd < 0)
              {
                perror("open");
                exit(errno);
              }
              //成功打开重定向文件
              dup2(fd,0);
            }
            break;
        case APPEND_REDIR:
        case OUT_REDIR:
            {
              int flags = O_WRONLY | O_CREAT;
              if(redirType == APPEND_REDIR ) flags |= O_APPEND; 
              else flags |= O_TRUNC;
              int fd = open(redirFile,flags,0666);

              if(fd < 0)
              {
                perror("open");
                exit(errno);
              }
              dup2(fd,1);
            }
            break;
        default:
            printf("dir error!\n");
            break;
      }

      execvp(myargv[0],myargv);   //执行程序替换的时候，会不会影响曾经重定向的文件？ 
      exit(1);
    }
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    assert(ret > 0);
    (void)ret;
    lastExitCode = ((status >> 8) & 0xFF);
    lastExitSignal = (status & 0x7F);
  }
  return 0;
}
